#ifndef GAME_H
#define GAME_H

#include <deque>
#include <set>

#include <QObject>
#include <QSize>
#include <QPoint>

inline bool operator< (const QPoint& lhs, const QPoint& rhs)
{
    if (lhs.x() == rhs.x())
        return lhs.y() < rhs.y();
    return lhs.x() < rhs.x();
}

enum class Direction {
    up,
    down,
    left,
    right,
    numDirections
};

inline Direction oppositeDirection(Direction d)
{
    if (d == Direction::up)
        return Direction::down;
    if (d == Direction::down)
        return Direction::up;
    if (d == Direction::left)
        return Direction::right;
    if (d == Direction::right)
        return Direction::left;
    __builtin_unreachable();
}

class GameView;

class Game : public QObject
{
    Q_OBJECT

public:
    static constexpr int defaultMapWidth = 40;
    static constexpr int defaultMapHeight = 40;
    static constexpr int minimumMapWidth = 3;
    static constexpr int minimumMapHeight = 3;

    static constexpr int updateInterval = 100;

    friend class GameView;
    Game(int mapWidth = defaultMapWidth, int mapHeight = defaultMapHeight);

    enum class State {
        initial,
        ready,
        started,
        paused,
        over,
        numStates
    };

    void reset();
    void ready();
    void start();
    void pause();
    void over();
    void restart();

    bool save(const QString& fileName);
    bool load(const QString& fileName);

    void addObstacleAt(int i, int j);
    void removeObstacleAt(int i, int j);
    bool hasObstacleAt(int i, int j) const;

    State getState() const { return state; }
    int getScore() const { return score; }
    int getMovedDistance() const { return time; }
    qreal getGameTime() const { return time * updateInterval * 0.001; }

signals:
    void gameStateChanged(Game::State prev, Game::State next);

protected:
    void timerEvent(QTimerEvent *event) override;

signals:
    void gameUpdate();

private:
    bool checkPosition(const QPoint& p) const;
    bool collideWithSnake(const QPoint& p) const;
    bool generateFood(int minDistance = 10, int maxTryTimes = 5000);
    QPoint getNextPosition(const QPoint& p, Direction d);
    void setSnakeDirection(Direction d);
    void dump() const; // not implemented

    State state;
    QSize mapSize;

    std::deque<QPoint> snake; // snake body parts
    Direction dir, lastDir; // current & last direction of the snake
    int growing;
    int score, time;
    std::set<QPoint> obstacles;
    QPoint food;

    int timerId;
};

#endif // GAME_H
